package com.witframework.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import javax.sql.DataSource;

import org.apache.log4j.Logger;

public class SmartDataSource implements DataSource {	
	private static long now=System.currentTimeMillis();
	private int maxPoolSize=150;	
	private int checkoutTimeout=30000;
	private DataSource realDataSource=null;
	private int leakTimeout=10;
	private static Vector<ConnectionLeakEvent> trackers = new Vector<ConnectionLeakEvent>();	
	private static Thread reaper = null;
	private static  Logger logger = Logger.getLogger(SmartDataSource.class);	
	public SmartDataSource(){	
		if(reaper==null&&leakTimeout>0){
			reaper = new Thread(new MonitoringProcess(),"MonitoringProcess");
			reaper.setPriority(Thread.MAX_PRIORITY);
			reaper.setDaemon(true);
			reaper.start();	
		}
	}
	public static void closeConnection(SmartProxyConnectionHandle spc) throws SQLException{		
		trackers.remove(spc.clv);
		spc.clv.getRealConnection().close();		
	}
	public Connection getConnection() throws SQLException {		
		return getConnection(null,null);
	}
	public Connection getConnection(String username, String password) throws SQLException {		
		if(leakTimeout>0&&trackers.size()>=maxPoolSize){			
			releaseMaxUsingTimeConnection();			
			if(trackers.size()>=maxPoolSize){
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					//no thing
				}
			}				
			return getConnection(username,password);
		}
		Connection realConnection;
		if(username==null)
			realConnection=realDataSource.getConnection();
		else realConnection=realDataSource.getConnection(username,password);
		return getProxyConnection(realConnection);
	}
	private Connection getProxyConnection(Connection realConnection){
		ConnectionLeakEvent evt=new ConnectionLeakEvent(realConnection,System.currentTimeMillis()-now);
		SmartProxyConnectionHandle proxyConnectionHandle=new SmartProxyConnectionHandle(evt);	
		Connection proxyConnection=proxyConnectionHandle.newProxyConnection();		
		trackers.add(evt);
		return proxyConnection;
	}
	private void releaseMaxUsingTimeConnection(){	
		try{
			if(trackers.size()>=maxPoolSize){				
				ConnectionLeakEvent elv=trackers.firstElement();
				if(elv!=null){
					if((System.currentTimeMillis()-now-elv.getLastAccessedTime())>=getLeakTimeout()){						
						
						trackers.remove(elv);		
						elv.getRealConnection().close();
						logger.warn("**  CRM. release a leaked connection.");
						
					}
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}	

	public PrintWriter getLogWriter() throws SQLException {
		return realDataSource.getLogWriter();
	}

	public int getLoginTimeout() throws SQLException {		
		return realDataSource.getLoginTimeout();
	}

	public void setLogWriter(PrintWriter out) throws SQLException {
		realDataSource.setLogWriter(out);
	}
	public void setLoginTimeout(int seconds) throws SQLException {
		realDataSource.setLoginTimeout(seconds);
	}
	public boolean isWrapperFor(Class<?> iface) throws SQLException {	
		return false;
	}
	public <T> T unwrap(Class<T> iface) throws SQLException {		
		return null;
	}
	private  void check(){
		int timeout=getLeakTimeout();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e){}	
		if(trackers.size()>=maxPoolSize*9/10){
			try {
				Iterator<ConnectionLeakEvent> evtIt=trackers.iterator();
				while(evtIt.hasNext()){
					ConnectionLeakEvent evt=evtIt.next();
					if((System.currentTimeMillis()-evt.getLastAccessedTime())>timeout){							
						evt.getRealConnection().close();
						evtIt.remove();							
						logger.warn("**  CRM. release a leaked connection.");									
					}
				}				
			} catch (SQLException e) {						
				e.printStackTrace();
			}	
		}		
	}
	private class MonitoringProcess implements Runnable{
		public MonitoringProcess(){
			logger.info("**************CRM. Running...");
		}
		public void run() {
			while(true){
				try{
					check();
				}catch(Exception e){
					//no thing
				}				
			}
		}
	}
	public int getLeakTimeout() {
		return leakTimeout*1000;
	}
	public void setLeakTimeout(int leakTimeout) {
		this.leakTimeout = leakTimeout;
	}
	
	public void setMaxPoolSize(int maxPoolSize) {
		this.maxPoolSize = maxPoolSize;		
	}	
	
	public int getMaxPoolSize() {		
		return maxPoolSize;
	}
	
	public static Collection<ConnectionLeakEvent> getTrackers() {
		return trackers;
	}	
	public static void setTrackers(Vector<ConnectionLeakEvent> trackers) {
		SmartDataSource.trackers = trackers;
	}

	public DataSource getRealDataSource() {
		return realDataSource;
	}


	public void setRealDataSource(DataSource dataSource) {
		this.realDataSource = dataSource;
	}
	public int getCheckoutTimeout() {
		return checkoutTimeout;
	}
	public void setCheckoutTimeout(int checkoutTimeout) {
		this.checkoutTimeout = checkoutTimeout;
	}	
}

class ConnectionLeakEvent {	
	private Connection realConnection;	
	private long lastAccessedTime;		
	public ConnectionLeakEvent(Connection conn, long lastAccessedTime) {
		super();
		this.realConnection = conn;
		this.lastAccessedTime = lastAccessedTime;
	}
	public Connection getRealConnection() {
		return realConnection;
	}
	public void setRealConnection(Connection conn) {
		this.realConnection = conn;
	}
	public long getLastAccessedTime() {
		return lastAccessedTime;
	}
	public void setLastAccessedTime(long lastAccessedTime) {
		this.lastAccessedTime = lastAccessedTime;
	}
}
